/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.beans; import java.beans.Introspector; import java.beans.IntrospectionException; import java.lang.reflect.Modifier; import java.text.MessageFormat; import java.util.ResourceBundle; import org.openide.src.ClassElement; import org.openide.src.FieldElement; import org.openide.src.MethodElement; import org.openide.src.MethodParameter; import org.openide.src.Type; import org.openide.src.SourceException; import org.openide.src.Identifier; import org.openide.src.ElementFormat; import org.openide.nodes.Node; import org.openide.TopManager; import org.openide.NotifyDescriptor; import org.openide.util.Utilities; import org.openide.util.NbBundle; //import org.netbeans.modules.java.support.AutoCommenter; /** Class representing a JavaBeans Property * @author Petr Hrebejk */ public class PropertyPattern extends Pattern { /** ResourceBundle */ private static final ResourceBundle bundle = NbBundle.getBundle( PropertyPattern.class ); /** Constant for READ/WRITE mode of properties */ public static final int READ_WRITE = 1; /** Constant for READ ONLY mode of properties */ public static final int READ_ONLY = 2; /** Constant for WRITE ONLY mode of properties */ public static final int WRITE_ONLY = 4; /** Getter method of this property */ protected MethodElement getterMethod = null; /** Setter method of this property */ protected MethodElement setterMethod = null; /** Field which probably belongs to this property */ protected FieldElement estimatedField = null; /** Holds the type of the property resolved from methods. */ protected Type type; /** Holds the decapitalized name. */ protected String name; /** Creates new PropertyPattern one of the methods may be null. * @param patternAnalyser patternAnalyser which creates this Property. * @param getterMethod getterMethod of the property or <CODE>null</CODE>. * @param setterMethod setterMethod of the property or <CODE>null</CODE>. * @throws IntrospectionException If specified methods do not follow beans Property rules. */ public PropertyPattern( PatternAnalyser patternAnalyser, MethodElement getterMethod, MethodElement setterMethod ) throws IntrospectionException { super( patternAnalyser ); this.getterMethod = getterMethod; this.setterMethod = setterMethod; type = findPropertyType(); name = findPropertyName(); } /** Creates new PropertyPattern. * @param patternAnalyser patternAnalyser which creates this Property. */ PropertyPattern( PatternAnalyser patternAnalyser ) { super( patternAnalyser ); } /** Creates new PropertyPattern. * @param patternAnalyser patternAnalyser which creates this Property. * @param name Name of the Property. * @param type Type of the Property. * @throws SourceException If the Property can't be created in the source. * @return Newly created PropertyPattern. */ static PropertyPattern create( PatternAnalyser patternAnalyser, String name, String type ) throws SourceException { PropertyPattern pp = new PropertyPattern( patternAnalyser ); pp.name = name; pp.type = Type.parse( type ); pp.generateGetterMethod(); pp.generateSetterMethod(); return pp; } /** Creates new property pattern with extended options * @param patternAnalyser patternAnalyser which creates this Property. * @param name Name of the Property. * @param type Type of the Property. * @param mode {@link #READ_WRITE Mode} of the new property. * @param bound Is the Property bound? * @param constrained Is the property constrained? * @param withField Should be the private field for this property genareted? * @param withReturn Generate return statement in getter? * @param withSet Generate seter statement for private field in setter. * @param withSupport Generate PropertyChange support? * @throws SourceException If the Property can't be created in the source. * @return Newly created PropertyPattern. */ static PropertyPattern create( PatternAnalyser patternAnalyser, String name, String type, int mode, boolean bound, boolean constrained, boolean withField, boolean withReturn, boolean withSet, boolean withSupport ) throws SourceException { PropertyPattern pp = new PropertyPattern( patternAnalyser ); pp.name = name; pp.type = Type.parse( type ); // Generate field if ( withField || withSupport ) { pp.generateField( true ); } // Ensure property change support field and methods exist String supportName = null; String vetoSupportName = null; if ( withSupport ) { if ( bound ) supportName = BeanPatternGenerator.supportField( pp.getDeclaringClass() ); if ( constrained ) vetoSupportName = BeanPatternGenerator.vetoSupportField( pp.getDeclaringClass() ); if ( bound ) BeanPatternGenerator.supportListenerMethods( pp.getDeclaringClass(), supportName ); if ( constrained ) BeanPatternGenerator.vetoSupportListenerMethods( pp.getDeclaringClass(), vetoSupportName ); } if ( mode == READ_WRITE || mode == READ_ONLY ) pp.generateGetterMethod( BeanPatternGenerator.propertyGetterBody( name, withReturn ), true ); if ( mode == READ_WRITE || mode == WRITE_ONLY ) pp.generateSetterMethod( BeanPatternGenerator.propertySetterBody( name, pp.getType(), bound, constrained, withSet, withSupport, supportName, vetoSupportName ), constrained, true ); return pp; } /** Gets the name of PropertyPattern * @return Name of the Property */ public String getName() { return name; } /** Sets the name of PropertyPattern * @param name New name of the property. * @throws SourceException If the modification of source code is impossible. */ public void setName( String name ) throws SourceException { if ( !Utilities.isJavaIdentifier( name ) ) throw new SourceException( "Invalid event source name" ); // NOI18N name = capitalizeFirstLetter( name ); if ( getterMethod != null ) { Identifier getterMethodID = Identifier.create(( getterMethod.getName().getName().startsWith("get") ? // NOI18N "get" : "is" ) + name ); // NOI18N getterMethod.setName( getterMethodID ); } if ( setterMethod != null ) { Identifier setterMethodID = Identifier.create( "set" + name ); // NOI18N setterMethod.setName( setterMethodID ); } this.name = Introspector.decapitalize( name ); // Ask if to set the estimated field if ( estimatedField != null ) { ElementFormat fmt = new ElementFormat ("{m} {t} {n}"); // NOI18N String mssg = MessageFormat.format( PatternNode.bundle.getString( "FMT_ChangeFieldName" ), new Object[] { fmt.format (estimatedField) } ); NotifyDescriptor nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION ); if ( TopManager.getDefault().notify( nd ).equals( NotifyDescriptor.YES_OPTION ) ) { estimatedField.setName( Identifier.create( Introspector.decapitalize( name ) ) ); } } } /** Returns the mode of the property {@link #READ_WRITE READ_WRITE}, {@link #READ_ONLY READ_ONLY} * or {@link #WRITE_ONLY WRITE_ONLY} * @return Mode of the property */ public int getMode() { if ( setterMethod != null && getterMethod != null ) return READ_WRITE; else if ( getterMethod != null && setterMethod == null ) return READ_ONLY; else if ( setterMethod != null && getterMethod == null ) return WRITE_ONLY; else return 0; } /** Sets the property to be writable * @param mode New Mode {@link #READ_WRITE READ_WRITE}, {@link #READ_ONLY READ_ONLY} * or {@link #WRITE_ONLY WRITE_ONLY} * @throws SourceException If the modification of source code is impossible. */ public void setMode( int mode ) throws SourceException { if ( getMode() == mode ) return; switch ( mode ) { case READ_WRITE: if ( getterMethod == null ) generateGetterMethod(); if ( setterMethod == null ) generateSetterMethod(); break; case READ_ONLY: if ( getterMethod == null ) generateGetterMethod(); if ( setterMethod != null ) deleteSetterMethod(); break; case WRITE_ONLY: if ( setterMethod == null ) generateSetterMethod(); if ( getterMethod != null ) deleteGetterMethod(); break; } } /** Returns the getter method * @return Getter method of the property */ public MethodElement getGetterMethod() { return getterMethod; } /** Returns the setter method * @return Setter method of the property */ public MethodElement getSetterMethod() { return setterMethod; } /** Gets the type of property * @return Type of the property */ public Type getType() { return type; } /** Sets the type of propertyPattern * @param type New type of the property * @throws SourceException If the modification of source code is impossible */ public void setType(Type type) throws SourceException { if ( this.type.compareTo( type, true ) ) return; if (getterMethod != null ) { if ( this.type.compareTo( Type.BOOLEAN, false ) ) { getterMethod.setName( Identifier.create( "get" + capitalizeFirstLetter( getName() ) ) ); // NOI18N } else if ( type.compareTo( Type.BOOLEAN, false ) ) { String mssg = MessageFormat.format( PatternNode.bundle.getString( "FMT_ChangeToIs" ), new Object[] { capitalizeFirstLetter( getName() ) } ); NotifyDescriptor nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION ); TopManager.getDefault().notify( nd ); if( nd.getValue().equals( NotifyDescriptor.YES_OPTION ) ) { getterMethod.setName( Identifier.create( "is" + capitalizeFirstLetter( getName() ) ) ); // NOI18N } } getterMethod.setReturn( type ); } if (setterMethod != null ) { MethodParameter[] params = setterMethod.getParameters(); if ( params.length > 0 ) { params[0].setType( type ); setterMethod.setParameters( params ); } } this.type = type; // Ask if to change estimated field Type if ( estimatedField != null ) { ElementFormat fmt = new ElementFormat ("{m} {t} {n}"); // NOI18N String mssg = MessageFormat.format( PatternNode.bundle.getString( "FMT_ChangeFieldType" ), new Object[] { fmt.format (estimatedField) } ); NotifyDescriptor nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION ); if ( TopManager.getDefault().notify( nd ).equals( NotifyDescriptor.YES_OPTION ) ) { estimatedField.setType(type); } else { estimatedField = null; } } } /** Gets the cookie of the first available method * @param cookieType Class of the Cookie * @return Cookie of Getter or Setter MethodElement */ public Node.Cookie getCookie( Class cookieType ) { if ( getterMethod != null ) return getterMethod.getCookie( cookieType ); if ( setterMethod != null ) return setterMethod.getCookie( cookieType ); return null; } /** Gets the estimated field * @return Field which (probably) belongs to the property. */ public FieldElement getEstimatedField( ) { return estimatedField; } /** Sets the estimated field * @param field Field for the property */ void setEstimatedField( FieldElement field ) { estimatedField = field; } /** Destroys methods associated methods with the pattern in source * @throws SourceException If modification of source is impossible */ public void destroy() throws SourceException { if ( estimatedField != null ) { ElementFormat fmt = new ElementFormat ("{m} {t} {n}"); // NOI18N String mssg = MessageFormat.format( PatternNode.bundle.getString( "FMT_DeleteField" ), new Object[] { fmt.format (estimatedField) } ); NotifyDescriptor nd = new NotifyDescriptor.Confirmation ( mssg, NotifyDescriptor.YES_NO_OPTION ); if ( TopManager.getDefault().notify( nd ).equals( NotifyDescriptor.YES_OPTION ) ) { deleteEstimatedField(); } } deleteGetterMethod(); deleteSetterMethod(); } // UTILITY METHODS ---------------------------------------------------------- /** Package private constructor. Merges two property descriptors. Where they * conflict, gives the second argument (y) priority over the first argumnet (x). * @param x The first (lower priority) PropertyPattern. * @param y The second (higher priority) PropertyPattern. */ PropertyPattern( PropertyPattern x, PropertyPattern y ) { super( y.patternAnalyser ); // Figure out the merged getterMethod MethodElement xr = x.getterMethod; MethodElement yr = y.getterMethod; getterMethod = xr; // Normaly give priority to y's getterMethod if ( yr != null ) { getterMethod = yr; } // However, if both x and y reference read method in the same class, // give priority to a boolean "is" method over boolean "get" method. // NOI18N if ( xr != null && yr != null && xr.getDeclaringClass() == yr.getDeclaringClass() && xr.getReturn().compareTo( Type.BOOLEAN, false ) && yr.getReturn().compareTo( Type.BOOLEAN, false ) && xr.getName().getName().indexOf("is") == 0 && // NOI18N yr.getName().getName().indexOf("get") == 0 ) { // NOI18N getterMethod = xr; } setterMethod = x.getSetterMethod(); if ( y.getSetterMethod() != null ) { setterMethod = y.getSetterMethod(); } // PENDING bound and constrained /* bound = x.bound | y.bound; constrained = x.constrained | y.constrained */ try { type = findPropertyType(); } catch ( IntrospectionException ex ) { //System.out.println (x.getName() + ":" + y.getName()); // NOI18N //System.out.println (x.getType() + ":" + y.getType() ); // NOI18N throw new InternalError( "Mixing invalid PropertyPattrens" + ex ); // NOI18N } name = findPropertyName(); } /** Resolves the type of the property from type of getter and setter. * @throws IntrospectionException if the property doesnt folow the design patterns * @return The type of the property. */ Type findPropertyType() throws IntrospectionException { Type resolvedType = null; if ( getterMethod != null ) { if ( getterMethod.getParameters().length != 0 ) { throw new IntrospectionException( "bad read method arg count" ); // NOI18N } resolvedType = getterMethod.getReturn(); if ( resolvedType.compareTo( Type.VOID, false ) ) { throw new IntrospectionException( "read method " + getterMethod.getName().getName() + // NOI18N " returns void" ); // NOI18N } } if ( setterMethod != null ) { MethodParameter params[] = setterMethod.getParameters(); if ( params.length != 1 ) { throw new IntrospectionException( "bad write method arg count" ); // NOI18N } if ( resolvedType != null && !resolvedType.compareTo( params[0].getType(), false ) ) { throw new IntrospectionException( "type mismatch between read and write methods" ); // NOI18N } resolvedType = params[0].getType(); } return resolvedType; } /** Based on names of getter and setter resolves the name of the property. * @return Name of the property */ String findPropertyName() { String methodName = null; if ( getterMethod != null ) methodName = getterMethod.getName().getName() ; else if ( setterMethod != null ) methodName = setterMethod.getName().getName() ; else { return null; } return methodName.startsWith( "is" ) ? // NOI18N Introspector.decapitalize( methodName.substring(2) ) : Introspector.decapitalize( methodName.substring(3) ); } // METHODS FOR GENERATING AND DELETING METHODS AND FIELDS-------------------- /** Generates getter method without body and without Javadoc comment. * @throws SourceException If modification of source code is impossible. */ void generateGetterMethod() throws SourceException { generateGetterMethod( null, false ); } /** Generates getter method with body and optionaly with Javadoc comment. * @param body Body of the method * @param javadoc Generate Javadoc comment? * @throws SourceException If modification of source code is impossible. */ void generateGetterMethod( String body, boolean javadoc ) throws SourceException { ClassElement declaringClass = getDeclaringClass(); MethodElement newGetter = new MethodElement(); newGetter.setName( Identifier.create( (type == Type.BOOLEAN ? "is" : "get") + capitalizeFirstLetter( getName() ) ) ); // NOI18N newGetter.setReturn( type ); newGetter.setModifiers( Modifier.PUBLIC ); if ( declaringClass.isInterface() ) { newGetter.setBody( null ); } else if ( body != null ) { newGetter.setBody( body ); } if ( javadoc ) { String comment = MessageFormat.format( bundle.getString( "COMMENT_PropertyGetter" ), new Object[] { getName() } ); newGetter.getJavaDoc().setRawText( comment ); } if ( declaringClass == null ) { //System.out.println ("nodecl - gen getter"); // NOI18N throw new SourceException(); } else { declaringClass.addMethod( newGetter ); getterMethod = declaringClass.getMethod( newGetter.getName(), getParameterTypes( newGetter ) ); } } /** Generates setter method without body and without Javadoc comment. * @throws SourceException If modification of source code is impossible. */ void generateSetterMethod() throws SourceException { generateSetterMethod( null, false, false ); } /** Generates setter method with body and optionaly with Javadoc comment. * @param body Body of the method * @param javadoc Generate Javadoc comment? * @param constrained Is the property constrained? * @throws SourceException If modification of source code is impossible. */ void generateSetterMethod( String body, boolean constrained, boolean javadoc ) throws SourceException { ClassElement declaringClass = getDeclaringClass(); MethodElement newSetter = new MethodElement(); newSetter.setName( Identifier.create( "set" + capitalizeFirstLetter( getName() ) ) ); // NOI18N newSetter.setReturn( Type.VOID ); newSetter.setModifiers( Modifier.PUBLIC ); newSetter.setParameters( ( new MethodParameter[] { new MethodParameter( name, type, false ) } )); if ( constrained ) newSetter.setExceptions( ( new Identifier[] { Identifier.create( "java.beans.PropertyVetoException" ) } ) ); // NOI18N if ( declaringClass.isInterface() ) { newSetter.setBody( null ); } else if ( body != null ) { newSetter.setBody( body ); } if ( javadoc ) { String comment = MessageFormat.format( bundle.getString( "COMMENT_PropertySetter" ), new Object[] { getName(), name } ); if ( constrained ) comment = comment + bundle.getString( "COMMENT_Tag_ThrowsPropertyVeto" ); newSetter.getJavaDoc().setRawText( comment ); } if ( declaringClass == null ) { //System.out.println ("nodecl - gen setter"); // NOI18N throw new SourceException(); } else { declaringClass.addMethod( newSetter ); setterMethod = declaringClass.getMethod( newSetter.getName(), getParameterTypes( newSetter ) ); } } /** Generates fied for the property. No javadoc comment is generated. * @throws SourceException If modification of source code is impossible. */ void generateField() throws SourceException { generateField( false ); } /** Generates fied for the property. * @param javadoc Generate javadoc comment? * @throws SourceException If modification of source code is impossible. */ void generateField( boolean javadoc ) throws SourceException { ClassElement declaringClass = getDeclaringClass(); FieldElement newField = new FieldElement(); newField.setName( Identifier.create( Introspector.decapitalize( getName() ) ) ); newField.setType( type ); newField.setModifiers( Modifier.PRIVATE ); if ( javadoc ) { String comment = MessageFormat.format( bundle.getString( "COMMENT_PropertyField" ), new Object[] { getName() } ); newField.getJavaDoc().setRawText( comment ); } if ( declaringClass == null ) { //System.out.println ("nodecl - gen setter"); // NOI18N throw new SourceException(); } else { declaringClass.addField( newField ); estimatedField = declaringClass.getField( newField.getName() ); } } /** Deletes the estimated field in source * @throws SourceException If modification of source code is impossible. */ void deleteEstimatedField() throws SourceException { if ( estimatedField == null ) return; ClassElement declaringClass = getDeclaringClass(); if ( declaringClass == null ) { //System.out.println ("nodecl"); // NOI18N throw new SourceException(); } else { declaringClass.removeField( estimatedField ); estimatedField = null; } } /** Deletes the setter method in source * @throws SourceException If modification of source code is impossible. */ void deleteGetterMethod() throws SourceException { if ( getterMethod == null ) return; ClassElement declaringClass = getDeclaringClass(); if ( declaringClass == null ) { throw new SourceException(); } else { declaringClass.removeMethod( getterMethod ); getterMethod = null; } } /** Deletes the setter method in source * @throws SourceException If modification of source code is impossible. */ void deleteSetterMethod() throws SourceException { if ( setterMethod == null ) return; ClassElement declaringClass = getDeclaringClass(); if ( declaringClass == null ) { throw new SourceException(); } else { declaringClass.removeMethod( setterMethod ); setterMethod = null; } } // UTILITY METHODS ---------------------------------------------------------- /** Utility method resturns array of types of parameters of a method * @param methodElement Method which parameter types should be resolved * @return Array of types of parameters */ static Type[] getParameterTypes( MethodElement methodElement ) { MethodParameter[] params = methodElement.getParameters(); Type[] types = new Type[ params == null ? 0 : params.length ]; for( int i = 0; i < params.length; i++ ) { types[i] = params[i].getType(); } return types; } /** Sets the properties to values of other property pattern. If the * properties change fires PropertyChange event. * @param src Source PropertyPattern it's properties will be copied. */ void copyProperties( PropertyPattern src ) { boolean changed = !src.getType().equals( getType() ) || !src.getName().equals( getName() ) || !(src.getMode() == getMode()) || !(src.getEstimatedField() == null ? estimatedField == null : src.getEstimatedField().equals( estimatedField ) ); if ( src.getGetterMethod() != getterMethod ) getterMethod = src.getGetterMethod(); if ( src.getSetterMethod() != setterMethod ) setterMethod = src.getSetterMethod(); if ( src.getEstimatedField() != estimatedField ) estimatedField = src.getEstimatedField(); if ( changed ) { try { type = findPropertyType(); } catch ( java.beans.IntrospectionException e ) { } name = findPropertyName(); firePropertyChange( new java.beans.PropertyChangeEvent( this, null, null, null ) ); } } } /* * Log * 13 Gandalf 1.12 1/15/00 Petr Hrebejk BugFix 5386, 5385, 5393 * and new WeakListener implementation * 12 Gandalf 1.11 1/13/00 Petr Hrebejk i18n mk3 * 11 Gandalf 1.10 1/12/00 Petr Hrebejk i18n * 10 Gandalf 1.9 1/4/00 Petr Hrebejk Various bugfixes - 5036, * 5044, 5045 * 9 Gandalf 1.8 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 8 Gandalf 1.7 10/10/99 Petr Hamernik console debug messages * removed. * 7 Gandalf 1.6 9/13/99 Petr Hrebejk Creating multiple * Properties/EventSet with the same name vorbiden. Forms made i18n * 6 Gandalf 1.5 7/29/99 Petr Hrebejk Fix - change * ReadOnly/WriteOnly to ReadWrite mode diddn't registered the added * methods properly * 5 Gandalf 1.4 7/28/99 Petr Hrebejk Property Mode change fix * 4 Gandalf 1.3 7/26/99 Petr Hrebejk Better implementation of * patterns resolving * 3 Gandalf 1.2 7/21/99 Petr Hrebejk Bug fixes interface * bodies, is for boolean etc * 2 Gandalf 1.1 7/20/99 Petr Hrebejk * 1 Gandalf 1.0 6/28/99 Petr Hrebejk * $ */